﻿using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Mono.Cecil;
using Mono.Cecil.Cil;
using NUnit.Core;
using NUnit.Core.Extensibility;
using NUnit.Framework;

namespace Rauchy.Oapt
{
    public class OneAssertTestDecorator : ITestDecorator
    {
        public Test Decorate(Test test, MemberInfo member)
        {
            if (Reflect.HasAttribute(member, typeof (ForEachAssertAttribute).FullName, false) &&
                member is MethodInfo)
            {
                var method = (MethodInfo) member;
                var suite = new TestSuite(method.Name);

                var counter = 0;
                foreach (var testName in GenereateTestNames(method))
                {
                    var testMethod = new OneAssertTestMethod(method, testName, counter++);
                    suite.Add(testMethod);
                }

                return suite;
            }
            else
            {
                return test;
            }
        }

        private static string ConvertToValidMethodName(string operand)
        {
            operand = operand.Replace(" ", "_");
            var builder = new StringBuilder();

            foreach (var match in new Regex(@"\w*").Matches(operand))
            {
                builder.Append(match.ToString());
            }

            return builder.ToString();
        }

        private static IEnumerable<string> GenereateTestNames(MethodInfo method)
        {
            var assemblyDefintion = AssemblyFactory.GetAssembly(Assembly.GetAssembly(method.DeclaringType).Location);
            var counter = 0;

            foreach (var typeDefinition in assemblyDefintion.MainModule.Types.Cast<TypeDefinition>())
            {
                foreach (var methodDefinition in typeDefinition.Methods.Cast<MethodDefinition>().
                    Where(x => x.Name.Contains(method.Name)))
                {
                    foreach (var instruction in methodDefinition.Body.Instructions.Cast<Instruction>().
                        Where(x => x.OpCode == OpCodes.Call &&
                                   x.Operand is MemberReference &&
                                   ((MemberReference) x.Operand).DeclaringType.FullName == typeof (Assert).FullName))
                    {
                        var suffix = instruction.Previous.OpCode == OpCodes.Ldstr
                                         ? ConvertToValidMethodName((string) instruction.Previous.Operand)
                                         : (counter + 1).ToString();
                        counter++;

                        yield return string.Format("{0}_{1}", method.Name, suffix);
                    }
                }
            }
        }
    }
}